// Fill out your copyright notice in the Description page of Project Settings.


#include "ProceduralSectorComponent.h"

// Sets default values for this component's properties
UProceduralSectorComponent::UProceduralSectorComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;

	// ...
}


// Called when the game starts
void UProceduralSectorComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	
}

void UProceduralSectorComponent::OnRegister()
{
	Super::OnRegister();

	AActor* Owner = this->GetOwner();
	check(Owner);

	SectorMeshComponent = NewObject<UProceduralMeshComponent>(this, "Mesh");
	SectorMeshComponent->SetFlags(RF_Transient);
	SectorMeshComponent->RegisterComponent();
	Owner->AddInstanceComponent(SectorMeshComponent);
	SectorMeshComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);

	TextComponent = NewObject<UTextRenderComponent>(this, "Label");
	TextComponent->SetFlags(RF_Transient);
	TextComponent->RegisterComponent();
	Owner->AddInstanceComponent(TextComponent);
	TextComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
}


// Called every frame
void UProceduralSectorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ...
}

void UProceduralSectorComponent::SetColor(const FLinearColor Color)
{
	if (MaterialInstance)
	{
		MaterialInstance->SetVectorParameterValue(TEXT("Color"), Color);
	}
}

void UProceduralSectorComponent::Rebuild(float Angle, float Radius, float Height, int Precision, float Bevel, UMaterialInterface* MaterialInterface)
{
	const float AngleStep = Angle / Precision;
	const float InitAngle = Angle / -2.0f;
	const float HalfHeight = Height / 2.0f;

	// 倒角不会超过高度的一半
	Bevel = (Bevel >= HalfHeight) ? HalfHeight : Bevel;

	// 计算顶点位置
	TArray<FVector> VertexPositions;

	FVector P0 = FVector(0, 0, HalfHeight);
	VertexPositions.Add(P0);

	for (int i = 0; i <= Precision; ++i)
	{
		FQuat RotationQuat = FQuat(FVector::UnitZ(), FMath::DegreesToRadians(InitAngle + AngleStep * i));
		FVector RotatedVector = RotationQuat.RotateVector(FVector::UnitX());
		FVector P1 = RotatedVector * (Radius - Bevel) + FVector::UnitZ() * HalfHeight;
		FVector P2 = RotatedVector * Radius + FVector::UnitZ() * (HalfHeight - Bevel);
		FVector P3 = RotatedVector * Radius - FVector::UnitZ() * (HalfHeight - Bevel);
		FVector P4 = RotatedVector * (Radius - Bevel) - FVector::UnitZ() * HalfHeight;

		VertexPositions.Add(P1);
		VertexPositions.Add(P2);
		VertexPositions.Add(P3);
		VertexPositions.Add(P4);
	}

	FVector Pe = FVector(0, 0, -HalfHeight);
	VertexPositions.Add(Pe);

	// 构建三角面
	int MeshSectionCount = 0;
	
	// Start Side Faces
	auto BuildStartSideFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;

		const FQuat RotationQuat = FQuat(FVector::UnitZ(), FMath::DegreesToRadians(InitAngle));
		const FVector RotatedVector = RotationQuat.RotateVector(FVector::UnitX());
		const FVector Normal = FVector::CrossProduct(RotatedVector, FVector::UnitZ()).GetSafeNormal();
		
		if (Bevel == 0)
		{
			int32 idx0 = 0;
			int32 idx1 = 1;
			int32 idx2 = 4;
			int32 idx3 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);

			Indices.Add(0);
			Indices.Add(1);
			Indices.Add(2);
			Indices.Add(0);
			Indices.Add(2);
			Indices.Add(3);

			for (int i = 0; i < 4; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		else if (Bevel == HalfHeight)
		{
			int32 idx0 = 0;
			int32 idx1 = 1;
			int32 idx2 = 2;
			int32 idx3 = 4;
			int32 idx4 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			Vertices.Add(VertexPositions[idx4]);

			Indices.Add(0);
			Indices.Add(1);
			Indices.Add(3);
			Indices.Add(1);
			Indices.Add(2);
			Indices.Add(3);
			Indices.Add(0);
			Indices.Add(3);
			Indices.Add(4);

			for (int i = 0; i < 5; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		else
		{
			int32 idx0 = 0;
			int32 idx1 = 1;
			int32 idx2 = 2;
			int32 idx3 = 3;
			int32 idx4 = 4;
			int32 idx5 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			Vertices.Add(VertexPositions[idx4]);
			Vertices.Add(VertexPositions[idx5]);

			Indices.Add(0);
			Indices.Add(1);
			Indices.Add(4);
			Indices.Add(0);
			Indices.Add(4);
			Indices.Add(5);
			Indices.Add(1);
			Indices.Add(2);
			Indices.Add(3);
			Indices.Add(1);
			Indices.Add(3);
			Indices.Add(4);

			for (int i = 0; i < 6; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		
		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
		
	};
	BuildStartSideFace();

	// End Side Faces
	auto BuildEndSideFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;

		const FQuat RotationQuat = FQuat(FVector::UnitZ(), FMath::DegreesToRadians(-InitAngle));
		const FVector RotatedVector = RotationQuat.RotateVector(FVector::UnitX());
		const FVector Normal = FVector::CrossProduct(FVector::UnitZ(), RotatedVector).GetSafeNormal();
		
		if (Bevel == 0)
		{
			int32 idx0 = 0;
			int32 idx1 = Precision * 4 + 1;
			int32 idx2 = Precision * 4 + 4;
			int32 idx3 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);

			Indices.Add(0);
			Indices.Add(3);
			Indices.Add(1);
			Indices.Add(1);
			Indices.Add(3);
			Indices.Add(2);

			for (int i = 0; i < 4; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		else if (Bevel == HalfHeight)
		{
			int32 idx0 = 0;
			int32 idx1 = Precision * 4 + 1;
			int32 idx2 = Precision * 4 + 2;
			int32 idx3 = Precision * 4 + 4;
			int32 idx4 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			Vertices.Add(VertexPositions[idx4]);
			
			Indices.Add(0);
			Indices.Add(4);
			Indices.Add(1);
			Indices.Add(1);
			Indices.Add(4);
			Indices.Add(3);
			Indices.Add(1);
			Indices.Add(3);
			Indices.Add(2);

			for (int i = 0; i < 5; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		else
		{
			int32 idx0 = 0;
			int32 idx1 = Precision * 4 + 1;
			int32 idx2 = Precision * 4 + 2;
			int32 idx3 = Precision * 4 + 3;
			int32 idx4 = Precision * 4 + 4;
			int32 idx5 = VertexPositions.Num() - 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			Vertices.Add(VertexPositions[idx4]);
			Vertices.Add(VertexPositions[idx5]);

			Indices.Add(0);
			Indices.Add(5);
			Indices.Add(1);
			Indices.Add(1);
			Indices.Add(5);
			Indices.Add(4);
			Indices.Add(1);
			Indices.Add(4);
			Indices.Add(2);
			Indices.Add(2);
			Indices.Add(4);
			Indices.Add(3);

			for (int i = 0; i < 6; ++i)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}
		
		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
		
	};
	BuildEndSideFace();

	auto BuildArcSideFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;
		
		for (int i = 0; i < Precision; ++i)
		{
			int32 idx0 = i * 4 + 2;;
			int32 idx1 = i * 4 + 3;
			int32 idx2 = (i + 1) * 4 + 2;
			int32 idx3 = (i + 1) * 4 + 3;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 2);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 1);

			FVector Normal = (VertexPositions[idx0] + VertexPositions[idx2]).GetSafeNormal();
			
			for (int j = 0; j < 4; ++j)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}

		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
	};

	// Arc Side Faces
	if (Bevel < HalfHeight)
	{
		BuildArcSideFace();
	}

	auto BuildTopFanFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;
		
		for (int i = 0; i < Precision; ++i)
		{
			int32 idx0 = 0;
			int32 idx1 = i * 4 + 1;
			int32 idx2 = (i + 1) * 4 + 1;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			
			Indices.Add(i * 3 + 0);
			Indices.Add(i * 3 + 2);
			Indices.Add(i * 3 + 1);
			
			FVector Normal = FVector::UnitZ();

			for (int j = 0; j < 3; ++j)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}

		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
	};

	// Top Faces
	BuildTopFanFace();

	// Bottom Faces
	auto BuildBottomFanFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;
		
		for (int i = 0; i < Precision; ++i)
		{
			int32 idx0 = VertexPositions.Num() - 1;
			int32 idx1 = i * 4 + 4;
			int32 idx2 = (i + 1) * 4 + 4;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			
			Indices.Add(i *3 + 0);
			Indices.Add(i *3 + 1);
			Indices.Add(i *3 + 2);

			FVector Normal = -FVector::UnitZ();

			for (int j = 0; j < 3; ++j)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}

		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
	};
	
	BuildBottomFanFace();

	auto BuildTopBevelFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;
		
		for (int i = 0; i < Precision; ++i)
		{
			int32 idx0 = i * 4 + 1;;
			int32 idx1 = i * 4 + 2;
			int32 idx2 = (i + 1) * 4 + 1;
			int32 idx3 = (i + 1) * 4 + 2;
			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 2);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 1);

			FVector Normal = ((VertexPositions[idx0] + VertexPositions[idx2]).GetSafeNormal() + FVector::UnitZ()).GetSafeNormal();
			
			for (int j = 0; j < 4; ++j)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}

		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
	};

	auto BuildBottomBevelFace = [this, &MeshSectionCount, &VertexPositions, &Bevel, &HalfHeight, &InitAngle, &Precision]()
	{
		TArray<FVector> Vertices;
		TArray<int32> Indices;
		TArray<FVector> Normals;
		TArray<FVector2D> UVs;
		TArray<FProcMeshTangent> Tangents;
		TArray<FLinearColor> VertexColors;

		for (int i = 0; i < Precision; ++i)
		{
			int32 idx0 = i * 4 + 3;;
			int32 idx1 = i * 4 + 4;
			int32 idx2 = (i + 1) * 4 + 3;
			int32 idx3 = (i + 1) * 4 + 4;

			
			Vertices.Add(VertexPositions[idx0]);
			Vertices.Add(VertexPositions[idx1]);
			Vertices.Add(VertexPositions[idx2]);
			Vertices.Add(VertexPositions[idx3]);
			
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 2);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 0);
			Indices.Add(i * 4 + 3);
			Indices.Add(i * 4 + 1);

			FVector Normal = ((VertexPositions[idx0] + VertexPositions[idx2]).GetSafeNormal() - FVector::UnitZ()).GetSafeNormal();
			
			for (int j = 0; j < 4; ++j)
			{
				Normals.Add(Normal);
				UVs.Add(FVector2D::ZeroVector);
				Tangents.Add(FProcMeshTangent(0, 1, 0));
				VertexColors.Add(FLinearColor::White);
			}
		}

		SectorMeshComponent->CreateMeshSection_LinearColor(MeshSectionCount++, Vertices, Indices, Normals, UVs, VertexColors, Tangents, false);
	};

	if (Bevel > 0)
	{
		// Top Bevel Faces
		BuildTopBevelFace();

		// Bottom Bevel Faces
		BuildBottomBevelFace();
	}
	
	if (MaterialInterface)
	{
		MaterialInstance = UMaterialInstanceDynamic::Create(MaterialInterface, this);

		for (int i = 0; i < MeshSectionCount; ++i)
		{
			SectorMeshComponent->SetMaterial(i, MaterialInstance);
		}
	}
}

void UProceduralSectorComponent::SetLabel(FString Label, float Radius, float Angle, UMaterialInterface* Material)
{
	TextComponent->SetMaterial(0, Material);
	TextComponent->SetText(FText::FromString(Label));
	TextComponent->SetHorizontalAlignment(EHorizTextAligment::EHTA_Center);
	TextComponent->SetVerticalAlignment(EVerticalTextAligment::EVRTA_TextCenter);
	TextComponent->SetWorldSize(18);
	TextComponent->SetCastShadow(false);
	TextComponent->SetRelativeRotation(FRotator(90.0f, 0, Angle));
	TextComponent->SetRelativeLocation(FVector::UnitX() * Radius + FVector::UnitZ() * 20.0f);
}
